from typing import Any, Dict, Optional, Tuple
import json

from flow_engine.core.graph import FlowGraph

# DTOs moved to routers/dto; service decoupled from schemas
from ..dao import create_flow, list_flows, get_flow, update_flow, delete_flow, create_run, update_run
from ..managers.engine_manager import create_runner, register_runner, unregister_runner
from ..utils import HAS_LANGGRAPH, to_mermaid_code


def create_flow_service(payload: Any) -> int:
    # validate name to avoid DB NOT NULL errors
    if not getattr(payload, "name", None) or str(payload.name).strip() == "":
        raise ValueError("Flow name is required")
    # validate by attempting to build graph and calling validate()
    try:
        graph = FlowGraph(payload.flow.model_dump())
        graph.validate()
    except Exception as e:
        raise ValueError(f"Invalid flow: {e}")
    created = create_flow(payload.name, payload.description, payload.flow.model_dump())
    return created["id"]


def list_flows_service() -> Dict[str, list]:
    return {"items": list_flows()}


def get_flow_service(flow_id: int) -> Optional[Dict[str, Any]]:
    return get_flow(flow_id)


def update_flow_service(payload: Any) -> bool:
    # validate before updating if flow provided
    flow_json = payload.flow.model_dump() if getattr(payload, "flow", None) is not None else None
    if flow_json is not None:
        try:
            graph = FlowGraph(flow_json)
            graph.validate()
        except Exception as e:
            raise ValueError(f"Invalid flow: {e}")
    updated = update_flow(payload.flow_id, name=payload.name, description=payload.description, flow_json=flow_json)
    return updated is not None


def delete_flow_service(flow_id: int) -> bool:
    return delete_flow(flow_id)


def validate_flow_service(flow_id: int) -> Dict[str, Any]:
    f = get_flow(flow_id)
    if not f:
        raise ValueError("Flow not found")
    try:
        flow_data = f.get("flow_json")
        if isinstance(flow_data, str):
            try:
                flow_data = json.loads(flow_data)
            except Exception:
                pass
        FlowGraph(flow_data).validate()
        return {"ok": True}
    except Exception as e:
        return {"ok": False, "error": str(e)}


def mermaid_service(flow: Dict[str, Any]) -> str:
    return to_mermaid_code(flow)


def run_flow_service(flow_id: int, params: Any) -> Tuple[int, callable]:
    f = get_flow(flow_id)
    if not f:
        raise ValueError("Flow not found")
    if params.engine == 'lang' and not HAS_LANGGRAPH:
        raise ValueError("LangGraph engine not available")
    run = create_run(flow_id=flow_id, status='pending', engine=params.engine, max_steps=params.max_steps, timeout=params.timeout, parallelism=params.parallelism, context_json=params.context)
    run_id = run["id"]

    def _run():
        try:
            flow_data = f.get("flow_json")
            if isinstance(flow_data, str):
                try:
                    flow_data = json.loads(flow_data)
                except Exception:
                    pass
            graph = FlowGraph(flow_data)
            runner = create_runner(graph, params.engine, params.max_steps, params.timeout, params.parallelism, flow_id=flow_id, run_id=run_id)
            register_runner(run_id, runner)
            # runners persist start/status internally
            result = runner.run(dict(params.context))
            # finish status persisted by runner; no need to update here
        except Exception as e:
            # still record error if runner didn't get chance
            update_run(run_id, status='error', finished_at=datetime.now(timezone.utc).replace(tzinfo=None).isoformat(), error=str(e))
        finally:
            unregister_runner(run_id)

    return run_id, _run


def run_demo_service(flow_id: int) -> Tuple[int, callable]:
    f = get_flow(flow_id)
    if not f:
        raise ValueError("Flow not found")
    run = create_run(flow_id=flow_id, status='pending', engine='demo', max_steps=1000, timeout=None, parallelism=None, context_json={})
    run_id = run["id"]

    def _run():
        try:
            flow_data = f.get("flow_json")
            if isinstance(flow_data, str):
                try:
                    flow_data = json.loads(flow_data)
                except Exception:
                    pass
            graph = FlowGraph(flow_data)
            runner = create_runner(graph, 'demo', 1000, None, None, flow_id=flow_id, run_id=run_id)
            register_runner(run_id, runner)
            result = runner.run({})
            # finish status persisted by runner
        except Exception as e:
            # 运行失败，记录错误并结束
            update_run(run_id, status='error', finished_at=datetime.now(timezone.utc).replace(tzinfo=None).isoformat(), error=str(e))
            raise
        finally:
            unregister_runner(run_id)

    return run_id, _run
from datetime import datetime, timezone